#include <QtCore>
#include <QtGui>

#include "PicMove.h"
#include "SettingDialog.h"

Action::Action(ActionType t, const QString &p, const QString &dest)
		: mType(t), mPath(p), mDest(dest)
{}

Action::ActionType Action::type() const	{	return mType;	}
QString Action::path() const	{	return mPath;	}
QString Action::destination() const	{	return mDest;	}

using namespace Qt;

PicMove::PicMove(QWidget *parent)
	: QMainWindow(parent)
{
	if (QFileInfo(qApp->applicationName() + ".ini").exists())
		settings = new QSettings(qApp->applicationName() + ".ini", QSettings::IniFormat, this);
	else
		settings = new QSettings(QSettings::IniFormat, QSettings::UserScope, qApp->organizationName(), qApp->applicationName(), this);

	setupMenu();
	setupUI();

	loadSettings();
}

void PicMove::setupUI()
{
	QWidget *central = new QWidget(this);
	this->setCentralWidget(central);
	QGridLayout *grid = new QGridLayout(central);

	int gr = 0;	// grid row count
	for (gr = 0; gr != MAX_DEST_COUNT; ++gr)
	{
		QLabel *l = nameLabels[gr] = new QLabel(central);
		l->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
		grid->addWidget(l, gr, 0, 1, 1);
	}

	// image preview label
	preview = new QLabel(central);
	preview->setText("Preview");
	preview->setAlignment(Qt::AlignCenter);
	preview->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
	grid->addWidget(preview, 0, 1, gr++, 1);

	this->setStatusBar(statusBar = new QStatusBar(this));
	statusBar->showMessage(tr("Ctrl + Key to Copy, Shift + Key to Move"));
}

void PicMove::setupMenu()
{
	QAction *aboutAction = new QAction(tr("This App"), this);
	connect(aboutAction, SIGNAL(triggered()), this, SLOT(about()));
	QAction *aboutQtAction = new QAction(tr("Qt"), this);
	connect(aboutQtAction, SIGNAL(triggered()), qApp, SLOT(aboutQt()));
	undoAction = new QAction(tr("Undo"), this);
	undoAction->setShortcuts(QList<QKeySequence>() << Key_Backspace << Key_PageUp << Key_Left);
	undoAction->setDisabled(true);
	connect(undoAction, SIGNAL(triggered()), this, SLOT(undo()));
	QAction *skipAction = new QAction(tr("Skip"), this);
	skipAction->setShortcuts(QList<QKeySequence>() << Key_Space << Key_PageDown << Key_Right);
	connect(skipAction, SIGNAL(triggered()), this, SLOT(skip()));
	QAction *settingsAction = new QAction(tr("Settings"), this);
	connect(settingsAction, SIGNAL(triggered()), this, SLOT(editDestinationDirectory()));
	QAction *startAction = new QAction(tr("Start"), this);
	startAction->setShortcuts(QList<QKeySequence>() << Key_O + ControlModifier);
	connect(startAction, SIGNAL(triggered()), this, SLOT(openDirectory()));

	QMenuBar *menuBar = new QMenuBar(this);
	this->setMenuBar(menuBar);

	QMenu *aboutMenu = new QMenu(tr("About"), menuBar);
	aboutMenu->addAction(aboutAction);
	aboutMenu->addAction(aboutQtAction);

	menuBar->addAction(startAction);
	menuBar->addAction(undoAction);
	menuBar->addAction(skipAction);
	menuBar->addAction(settingsAction);
	menuBar->addAction(aboutMenu->menuAction());
}

PicMove::~PicMove()
{
	saveSettings();
}

void PicMove::about()
{
	QString message = tr("%1\nby %2\nVersion: %3\nQt Version: %4\nCompiled with gcc %5.%6.%7 on %8")
					  .arg(qApp->applicationName())
					  .arg(qApp->organizationName())
					  .arg(qApp->applicationVersion())
					  .arg(QT_VERSION_STR)
					  .arg(__GNUC__)
					  .arg(__GNUC_MINOR__)
					  .arg(__GNUC_PATCHLEVEL__)
					  .arg(__DATE__);
	QMessageBox::information(this, tr("About"), message);
}

void PicMove::loadSettings()
{
	for (int i = 0; i != MAX_DEST_COUNT; ++i)
		nameLabels[i]->setText(QString("%1: %2").arg(QKeySequence(ShortcutKeys[i]).toString()).arg(settings->value(QString("Dest%1/name").arg(i)).toString()));

	settings->beginGroup("General");
	this->resize(settings->value("width", 600).toInt(), settings->value("height", 400).toInt());
	if (settings->value("maximized", false).toBool())	this->showMaximized();
	settings->endGroup();
}

void PicMove::saveSettings()
{
	settings->beginGroup("General");
	settings->setValue("maximized", this->isMaximized());
	if (!this->isMaximized())
	{
		settings->setValue("width", this->width());
		settings->setValue("height", this->height());
	}
	settings->endGroup();
}

void PicMove::openDirectory()
{
	statusBar->clearMessage();
	skippedPath.clear();
	actionStack.clear();
	currentFilePath.clear();
	currentDirPath.clear();

	currentDirPath = QFileDialog::getExistingDirectory();
	next();
}

void PicMove::editDestinationDirectory()
{
	SettingDialog d(settings, this);
	if (d.exec() == QDialog::Rejected)	return;
	loadSettings();
}

void PicMove::keyPressEvent(QKeyEvent *e)
{
	if (currentFilePath.isEmpty())	return;
	if (e->isAutoRepeat())	return;

	const bool overwrite = settings->value("General/overwrite", false).toBool();
	const bool copyAsDefault = settings->value("General/copy_as_default", false).toBool();

	int i;
	const int count = (sizeof ShortcutKeys / sizeof ShortcutKeys[0]);
	for (i = 0; i != count; ++i)
		if (ShortcutKeys[i] == e->key())	break;
	if (i == count)	return;

	bool move = !copyAsDefault;
	if (e->modifiers() & Qt::ControlModifier)
		move = false;
	else if (e->modifiers() & Qt::ShiftModifier)
		move = true;

	const QString dest = settings->value(QString("Dest%1/path").arg(i)).toString();
	copyFile(currentFilePath, dest, overwrite, move);

	actionStack.push(Action((move ? Action::ActionMove : Action::ActionCopy), currentFilePath, dest));
	undoAction->setEnabled(true);

	(move ? next() : skip());
}

bool PicMove::next()
{
	if (currentDirPath.isNull())	return false;
	QStringList files = QDir(currentDirPath).entryList(QStringList() << "*.jpg" << "*.jpeg" << "*.png" << "*.gif", QDir::Files | QDir::Readable);
	int i;
	for (i = 0; i != files.size(); ++i)
	{
		currentFilePath = currentDirPath + "/" + files[i];
		if (skippedPath.contains(currentFilePath))	continue;
		QPixmap pix(currentFilePath);
		if (pix.isNull())	continue;
		statusBar->showMessage(QString("%1/%2 - %3x%4 - %5").arg(i + 1).arg(files.size()).arg(pix.width()).arg(pix.height()).arg(currentFilePath));
		preview->setPixmap(pix.scaled(preview->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation));
		break;
	}

	if (i == files.size())
	{
		QMessageBox::information(this, tr("Last File"), tr("This file is the last one."));
		return false;
	}

	return true;
}

bool PicMove::undo()
{
	// copy: stack << copy << skip
	// move: stack << move
	// skip: stack << skip
	if (actionStack.isEmpty())	return false;

	Action &action = actionStack.top();
	switch (action.type())
	{
		case Action::ActionMove:
			if (copyFile(action.destination() + "/" + QFileInfo(action.path()).fileName(), QFileInfo(action.path()).path(), false, true))
			{
				actionStack.pop();
				next();
			}
			else
				QMessageBox::critical(this, tr("Undo Error"), tr("Could not move the file."));
			break;

		case Action::ActionCopy:
			if (QFile(action.destination()).remove())
			{
				actionStack.pop();
				next();
			}
			else
				QMessageBox::critical(this, tr("Undo Error"), tr("Could not remove the copied file."));
			break;

		case Action::ActionSkip:
			if (skippedPath.size())	skippedPath.removeLast();
			actionStack.pop();
			if (!actionStack.isEmpty() && actionStack.top().type() == Action::ActionCopy)
				undo();
			next();
			break;

		default:
			return false;
	}

	undoAction->setEnabled(!actionStack.isEmpty());

	return true;
}

bool PicMove::skip()
{
	if (currentFilePath.isNull())	return false;
	skippedPath.append(currentFilePath);
	actionStack.push(Action(Action::ActionSkip, currentFilePath));
	undoAction->setEnabled(true);
	if (next() == false)	undo();	// 最後のファイルなのにundoが有効になる問題
	return true;
}

/*
	http://www.qtforum.org/article/2921/copyfile-function-for-qt.html
*/
bool PicMove::copyFile(QString source, QString destination, bool overwrite, bool move)
{
	QFileInfo sourceInfo = QFileInfo(source);
	QFileInfo destinationInfo = QFileInfo(destination);

	if(!sourceInfo.exists())
	{
		QMessageBox::critical(this, tr("File Copy"), tr("File Copy Failed - Source File does not exists."));
		return false;
	}
	else if(!sourceInfo.isFile())
	{
		QMessageBox::critical(this, tr("File Copy"), tr("File Copy Failed - Source is not a file."));
		return false;
	}
	else if(!destinationInfo.exists())
	{
		QMessageBox::critical(this, tr("File Copy"), tr("File Copy Failed - Destination cannot be found."));
		return false;
	}

	// if destination is directory then use same file name QFileInfo::fileName()
	if(destinationInfo.isDir())
		destination = (QFileInfo(QDir(destinationInfo.absoluteFilePath()), sourceInfo.fileName())).filePath();
	else if(!destinationInfo.isFile())
	{
		QMessageBox::critical(this, tr("File Copy"), tr("File Copy failed - Destination is neither a file or directory."));
		return false; // its neither file nor directory
	}

	// if destination file exists and overwrite disabled - return false
	if(!overwrite && QFile::exists(destination))
	{
		QMessageBox::critical(this, tr("File Copy"), tr("File Copy failed - Destination file exists, overwrite disabled."));
		return false; // its neither file nor directory
	}

	QFile sourceFile(source);
	QFile destinationFile(destination);

	if(overwrite)
	{
		if(!destinationFile.remove())
		{// failed to remove, abort
			sourceFile.close();
			destinationFile.close();
			QMessageBox::critical(this, tr("File Copy"), tr("File Copy failed - Destination file could not be removed."));
			return false;
		}
	}

	if(sourceFile.open(QIODevice::ReadOnly) && destinationFile.open(QIODevice::WriteOnly))
	{
		// show progress
		QProgressDialog progress(tr("Copying file..."), tr("Abort Copy"), 0, (ulong)sourceFile.size(), this);
		uint dataLength = 4096;
		char *data = new char[dataLength];
		ulong completed = 0;
		progress.setValue(completed);
		while(!sourceFile.atEnd())
		{
			completed += dataLength;
			destinationFile.write(data, sourceFile.read(data, dataLength));
			destinationFile.flush();
			progress.setValue(completed);
			if(progress.wasCanceled())
			{
				destinationFile.remove();
				progress.setValue((ulong)sourceFile.size());
				sourceFile.close();
				destinationFile.close();
				delete[] data;
				return false;
			}
		}
		delete[] data;
		progress.setValue((ulong)sourceFile.size());
		if(move)
		{
			if(!sourceFile.remove())
			{
				destinationFile.remove();
				sourceFile.close();
				destinationFile.close();
				QMessageBox::critical(this, tr("File Copy"), tr("File Copy failed - Source file could not be removed."));
				return false;
			}
		}
		sourceFile.close();
		destinationFile.close();
	}
	else
	{
		sourceFile.close();
		destinationFile.close();
		qDebug("DBG: Error - Source or destination file could not be opened.");
		QMessageBox::critical(this, tr("File Copy"), tr("File Copy failed - Source or destination file could not be opened."));
		return false;
	}
	return true;
}
